package com.eedi.framework.permission.service;

import cn.hutool.core.collection.CollUtil;
import com.google.common.annotations.VisibleForTesting;
import com.eedi.framework.common.enums.YesOrNoEnum;
import com.eedi.framework.common.util.collection.CollectionUtils;
import com.eedi.framework.common.util.object.BeanUtils;
import com.eedi.framework.permission.controller.sys.vo.menu.SysMenuListReq;
import com.eedi.framework.permission.controller.sys.vo.menu.SysMenuSaveReq;
import com.eedi.framework.permission.dal.SysPermissionCacheKeyConstants;
import com.eedi.framework.permission.dal.dataobject.SysMenuDO;
import com.eedi.framework.permission.dal.mapper.SysMenuMapper;
import com.eedi.framework.permission.enums.SysMenuTypeEnum;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static com.eedi.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.eedi.framework.common.util.collection.CollectionUtils.convertList;
import static com.eedi.framework.permission.dal.dataobject.SysMenuDO.SYS_ID_ROOT;
import static com.eedi.framework.permission.enums.SysPermissionErrorCodeConstants.*;

/**
 * 菜单 Service 实现
 *
 * @author 永聚长青源码
 */
@Service
@Slf4j
public class SysMenuServiceImpl implements SysMenuService {

    @Resource
    private SysMenuMapper sysMenuMapper;


    @Override
    @CacheEvict(value = SysPermissionCacheKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.sysMenuPermission",
            condition = "#createReqVO.sysMenuPermission != null")
    public String createMenu(SysMenuSaveReq createReqVO) {
        // 校验父菜单存在
        validateParentMenu(createReqVO.getParentSysMenuId(), null);
        // 校验菜单（自己）
        validateMenu(createReqVO.getParentSysMenuId(), createReqVO.getSysMenuName(), null);

        // 插入数据库
        SysMenuDO menu = BeanUtils.toBean(createReqVO, SysMenuDO.class);
        initMenuProperty(menu);
        sysMenuMapper.insert(menu);
        // 返回
        return menu.getSysMenuId();
    }

    @Override
    @CacheEvict(value = SysPermissionCacheKeyConstants.PERMISSION_MENU_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为 permission 如果变更，涉及到新老两个 permission。直接清理，简单有效
    public void updateMenu(SysMenuSaveReq updateReqVO) {
        // 校验更新的菜单是否存在
        if (sysMenuMapper.selectById(updateReqVO.getSysMenuId()) == null) {
            throw exception(SYS_MENU_NOT_EXISTS);
        }
        // 校验父菜单存在
        validateParentMenu(updateReqVO.getParentSysMenuId(), updateReqVO.getSysMenuId());
        // 校验菜单（自己）
        validateMenu(updateReqVO.getParentSysMenuId(), updateReqVO.getSysMenuName(), updateReqVO.getSysMenuId());

        // 更新到数据库
        SysMenuDO updateObj = BeanUtils.toBean(updateReqVO, SysMenuDO.class);
        initMenuProperty(updateObj);
        sysMenuMapper.updateById(updateObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysPermissionCacheKeyConstants.PERMISSION_MENU_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为此时不知道 id 对应的 permission 是多少。直接清理，简单有效
    public void deleteMenu(String menuId) {
        // 校验是否还有子菜单
        if (sysMenuMapper.selectCountByParentId(menuId) > 0) {
            throw exception(SYS_MENU_EXISTS_CHILDREN);
        }
        // 校验删除的菜单是否存在
        if (sysMenuMapper.selectById(menuId) == null) {
            throw exception(SYS_MENU_NOT_EXISTS);
        }
        // 标记删除
        sysMenuMapper.deleteById(menuId);
    }

    @Override
    public List<SysMenuDO> getMenuList() {
        return sysMenuMapper.selectList();
    }

    @Override
    public List<SysMenuDO> getMenuListByTenant(SysMenuListReq reqVO) {
        List<SysMenuDO> menus = getMenuList(reqVO);
        // 开启多租户的情况下，需要过滤掉未开通的菜单
//        tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
        return menus;
    }

    @Override
    public List<SysMenuDO> getMenuList(SysMenuListReq reqVO) {
        return sysMenuMapper.selectList(reqVO);
    }

    @Override
    @Cacheable(value = SysPermissionCacheKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
    public List<String> getMenuIdListByPermissionFromCache(String permission) {
        List<SysMenuDO> menus = sysMenuMapper.selectListByPermission(permission);
        return convertList(menus, SysMenuDO::getSysMenuId);
    }

    @Override
    public SysMenuDO getMenu(String menuId) {
        return sysMenuMapper.selectById(menuId);
    }

    @Override
    public List<SysMenuDO> getMenuList(Collection<String> menuIds) {
        if(CollectionUtils.isAnyEmpty(menuIds)){
            return Collections.emptyList();
        }
        return sysMenuMapper.selectBatchIds(menuIds);
    }

    @Override
    public void validateMenuIds(Collection<String> menuIds) {
        List<SysMenuDO> menuList = getMenuList(menuIds);
        if (CollUtil.isEmpty(menuList)){
            throw exception(SYS_MENU_NOT_EXISTS);
        }
        for (String menuId : menuIds){
            if (!menuIds.contains(menuId)){
                throw exception(SYS_MENU_NOT_EXISTS);
            }
        }
    }

    /**
     * 校验父菜单是否合法
     * <p>
     * 1. 不能设置自己为父菜单
     * 2. 父菜单不存在
     * 3. 父菜单必须是 {@link SysMenuTypeEnum#MENU} 菜单类型
     *
     * @param parentId 父菜单编号
     * @param childId  当前菜单编号
     */
    @VisibleForTesting
    void validateParentMenu(String parentId, String childId) {
        if (parentId == null || SYS_ID_ROOT.equals(parentId)) {
            return;
        }
        // 不能设置自己为父菜单
        if (parentId.equals(childId)) {
            throw exception(SYS_MENU_PARENT_ERROR);
        }
        SysMenuDO menu = sysMenuMapper.selectById(parentId);
        // 父菜单不存在
        if (menu == null) {
            throw exception(SYS_MENU_PARENT_NOT_EXISTS);
        }
        // 父菜单必须是目录或者菜单类型
        if (!SysMenuTypeEnum.DIR.equals(menu.getSysMenuType())
                && !SysMenuTypeEnum.MENU.equals(menu.getSysMenuType())) {
            throw exception(SYS_MENU_PARENT_NOT_DIR_OR_MENU);
        }
    }

    /**
     * 校验菜单是否合法
     * <p>
     * 1. 校验相同父菜单编号下，是否存在相同的菜单名
     *
     * @param name     菜单名字
     * @param parentId 父菜单编号
     * @param menuId       菜单编号
     */
    @VisibleForTesting
    void validateMenu(String parentId, String name, String menuId) {
        SysMenuDO menu = sysMenuMapper.selectByParentIdAndName(parentId, name);
        if (menu == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的菜单
        if (StringUtil.isEmpty(menuId)) {
            throw exception(SYS_MENU_NAME_DUPLICATE);
        }
        if (!menu.getSysMenuId().equals(menuId)) {
            throw exception(SYS_MENU_NAME_DUPLICATE);
        }
    }

    /**
     * 初始化菜单的通用属性。
     * <p>
     * 例如说，只有目录或者菜单类型的菜单，才设置 icon
     *
     * @param menu 菜单
     */
    private void initMenuProperty(SysMenuDO menu) {
        // 菜单为按钮类型时，无需 component、icon、path 属性，进行置空
        if (SysMenuTypeEnum.BUTTON.equals(menu.getSysMenuType())) {
            menu.setSysMenuComponent("");
            menu.setSysMenuComponentName("");
            menu.setSysMenuIcon("");
            menu.setSysMenuPath("");
            menu.setSysMenuVisible(YesOrNoEnum.YES);
            menu.setSysMenuKeepAlive(YesOrNoEnum.YES);
            menu.setSysMenuAlwaysShow(YesOrNoEnum.YES);
        }
    }

}
